PureBasic Survival Guide IV - Primer II
PureBasic Survival Guide
a tutorial for using purebasic for windows 5.70 LTS

Part 0 - TOC
Part I - General
Part II - Converts
Part III - Primer I
Part IV - Primer II
Part V - Advanced
Part VI - 2D Graphics I
Part VII - 2D Graphics II
Part X - Assembly
Part XI - Debugger
Part XII - VirtualBox
Part XIII - Databases
Part XIV - Networking
Part XV - Regular Expressions
Part XVI - Application Data
Part XVII - DPI
Part XXVII - Irregular Expressions
Part XXIX - Projects
 

Part IV - Primer II
v6.03 26.01.2016

4.1 Programming (in) style
4.2 Windows
4.3 ID vs. Number vs. handle
4.4 Events
4.5 Gadgets
4.6 Menus and toolbars
4.7 Include
4.8 Enumeration
4.9 Visual Designer
4.10 File system and FTP
4.11 Desktop and Mouse
 


... and you did read Primer I first, didn't you? :-)

In this sequel to the Primer, imaginatively calld Primer II (damn, I'm good) we're going to deal with a little more complicated matters, do a little on screen and check out the wonderful world of files.


4.1 Programming (in) style
 

With all the stuff from Primer I under our belt, we're ready to get serious and start writing some real stuff. Now is the right time to consider something important and often ignored aspect... your writing style.

When you're a newcomer to programming you probably haven't got a clue how to format your code, but neither do you have any bad habits that you have to get rid of. If you're an old hat this is the right moment to consider your style, and perhaps reconsider your style.
 

Style's just for En Vogue readers.

Nope, it's not. Style is what keeps code readable, even after a number of years, by other persons who don't even speak the same (programming?) language... Best of all, using a consistent style will save you time, and help you solve bugs much quicker.

I'm not going to tell you to use a certain style. That's entirely up to you. (Well, actually I am, but feel fry to cause yourself some troubles :-)) Here are just a few suggestions...


Indenting

This is ugly:

; survival guide 4_1_100 style
; pb 4.40b1
;
for n = 1 to 10
q = 1-q
select q
case 1
if n > 5 : a = a+n : endif
endselect
next n
Fortunately, a good editor handles this for you. Here are three options:
  • in the PB IDE, highlight the section you want to reformat and press [Ctrl] + [I]
  • if you're using CodeCaddy press [Alt] + [R]
This will result in something like:
; survival guide 4_1_110 style
; pb 4.40b1
;
For n = 1 To 10
  q = 1-q
  Select q
    Case 1
      If n > 5 : a = a+n : EndIf
  EndSelect
Next n
Much better!
 

Colon

The nice thing about programming in Basic is, well, it's basic. That means most keywords related to flow control come in pairs, so there's little discussion of when to indent and when not. You can see how everything inside a block is indented so you can easily match up the 'pairs' of if / endif, for / next etc.

Some programmers like to enter multiple statements on a single line (using the colon ':'). Here's a horrid example:

; survival guide 4_1_120 style
; pb 4.40b1
;
For n = 1 To 10 : q = 1-q : Select q : Case 1 : If n > 5 : a = a+n : EndIf : EndSelect : Next n
It's a matter of taste, but especially beginners should try NOT to do that. (Which is why I didn't mention the colon before :-)) Here's another way to indent the first example above, and this time I've chosen to break the If / Then line apart:
; survival guide 4_1_130 style
; pb 4.40b1
;
For n = 1 To 10
  q = 1-q
  Select q
    Case 1
     If n > 5
       a = a+n
     EndIf
  EndSelect
Next n
Ah, that's better!


Variable and procedure names

Obvously it does make sense to name variables and procedures in such a way that they make sense if you want to re-read or re-use your code. The use of 'simple' names (as 'n.l', 'c.l' etc.) is great for short, compact loops and areas where you can clearly see what the variable is or does. Once code gets longer / more complex, it's better to replace them with meaningful names ('loopcounter.l', 'customernumber.l' etc.).

To further clarify the meaning of a variable, you could make use of capitals, or underscores:

customernumber = 100     ; difficult to read
CustomerNumber = 100     ; a better way to do it
customer_number = 100    ; an alternative way to do it
In C(++) and Windows API the use of capitals is common, while for example (former) GfaBasic programmers tend to use underscore. The same applies to procedure names and parameters. First the 'ugly' version that nobody will understand in five years...
; survival guide 4_1_200 style
; pb 4.40b3
;
; bad doggy!
;
Procedure x21(a.s,x.l)
  z = x*2
  Debug "customer"
  Debug a
  Debug "will be billed"
  Debug z
  ProcedureReturn z
EndProcedure
;
x21("Fred",79)
It's probably better to to it this way:
; survival guide 4_1_210 style
; pb 4.40b3
;
; nice fido...
;
Procedure BillCustomer(CustomerName.s,Amount.l)
  Protected CorrectedAmount.l
  ;
  ; *** send bill to the customer
  ;
  ; in:     CustomerName.s   - name of the customer, max. 32 characters
  ;         Amount.l         - amount before correction
  ; retval:                  - amount after correction
  ;
  ; notes: i don't think this is the most complex procedure ever written...
  ;
  CorrectedAmount = Amount*2
  ;
  Debug "customer"
  Debug CustomerName
  Debug "will be billed"
  Debug CorrectedAmount
  ProcedureReturn CorrectedAmount
Endprocedure
;
BillCustomer("Fred",79)
If you edit your code in the PB IDE, you can see in the bottom line of your window the exact sequence and naming of your procedure's parameter. When you were entering the line...
BillCustomer("Fred",79)
... you could see at the bottom:
Line: 26 Colum:7 BillCustomer(CustomerName.s,Amount.l)
Using sensible names makes you work faster as you do not have to look inside the procedure to see what each parameter is supposed to do.


Folding

Wow. I just realized I haven't talked about folding yet (except here but you might have missed that). Better do it now.

Load the code above, and place the cursor on the line with the 'Procedure' statement. Now hit [F4] or click on the 'minus' symbol....Whohaaa! Hit it again!

It turns out you can fold and unfold procedures at will. If you have large blocks of code this turns out to be a great feature to keep an overview of your work. (You can use folding on other things as well, play around with the editor settings if you dare.)

You can (un)fold ALL procedures in your code at once using [Ctrl] + [F4].


In-procedure documenting

I decided to add this little section, as it simply does make sense (yet few people do it, it seems). As you can see in the 'nice fido' example above, I've added a few addtional comment lines immediately after the start. Although I've tried to pick smart, meaningfull parameter names (such as 'CustomerName' and 'Amount') there may be some additional information on those parameters. What's the range? What does the procedure actually do? What is the result returned?

By adding those few lines at the start of the procedure you can save yourself (and others) a lot of headaches. Just go to the procedure, unfold it if necessary, and check out those comments.

In the PB IDE you don't have to look for the procedure yoruself, the IDE will jump towards the proper line. Try it. Use the 'nice fido' code above again, and place the cursor on line 24 on 'BillCustomer'. Now press [Ctrl] and doubleclick. Et voila, the IDE jumps towards the line where you defined that procedure.

To go back to where you came from, hit [Ctrl] + [L].

Those keyboard shortcuts make life a lot easier. I've listed a few here.

CodeCaddy can reformat your code, though originally it wasn't written for that purpose. Its original intention was to have an instant 'lookup' tool. With CodeCaddy you put a cursor on any statement or command, and hit [Ctrl] + [F1]. CodeCaddy will then pop up and show you the relevant piece of code (even if it's part of an include that you haven't opened yet in the IDE). Why do I mention this here? Because this section is about documenting. It's a good idea to include some comments and a good description of the procedure parameters at the start of your procedures, immediately after the Procedure statement. Using either the PB IDE with [Ctrl] + doubleclick, or CodeCaddy with [Ctrl] + [F1] you then have a quick reference immediately under your fingertips.


Global and local

Variables declared and used within procedures are local. Unless stated otherwise.

If you write code you want to re-use, you may consider using the Protected keyword with a list of all local variables. This allows for two things:

1. you cannot accidently change a global variable that uses the same name
2. at the start of your procedure you always have all your variable types declared right
To beginners I would suggest not to use the Shared keyword, as bugs might be hard to trace when (re)using older code. Better use the Global keyword in those cases. Unless you're an expert, of course.

In normal code, you should consider starting at the top of your code with a list of all constants and global variables. If you're writing a large collection of small procedures you want to use in other code (a very common practice amongst programmers :-)) you may consider adding a list of global variables to the beginning of the procedure as well, if only as a reference and reminder of the global variables used within that specific procedure.

I would suggest to always use Protected as the second line of your procedure (immediately below the line with Procedure()) to define all local variables inside that procedure. You may consider adding a second Global line to list all global variables you access from within this procedure. I tend to do that in 'reusable' procedures in include files. Here is (another) example (yes, I just talked about this a minute ago here, it's either my old age or because I think it is so important...):

Procedure.l here_is_a_sample(race.s,driver_number.l,start_position.l,bad_weather.l)
  Protected a.l, b.l
  Global throttle_limit.l, torque.l, driver_name.s
  ;
  ; *** stupid useless procedure
  ; 
  ; in:      race.s             - name of the race
  ;          driver_number.l    - driver number, 1 to 255
  ;          start_position.l   - position on the starting grid, 1 to 100
  ;          bad_weather.l = 0  - good weather
  ;                        = -1 - random weather
  ;                        = 1  - so so weather
  ;                        = n  - increasingly bad weather, up to 100 = unbearable
  ; retval: .l = 0              - have no clue but it's zero
  ;            < 0              - no clue either but below zero must mean something
  ; out:    throttle_limit.l    - some global variable baffected by this procedure
  ;
  ; notes:
  ;
  ; - note 1
  ; - note 2
  ;
  ...
  ...
As a generic approach, I tend to list all local procedure parameters in the second line immediately below the Procedure() statement, followed by the third line with a list of all global variables being accessed. (Yes, I know it is not necessary to list globals again, it's just for my own reference.) Yes, I know. As a generic approach, I tend to list all procedure parameters with more detailed use.

In the first few lines of the procedure I use multiple comment lines, to assist my failing memory on the workings of the procedure. The first three blocks I use like this:

  • In the 'in:' block I list all procedure parameters and their values and funcitons.
  • In the 'retval' block I list eventual return values.
  • In the 'out' block I list global variables that may be affected by this procedure.
This, of course, is the way I do it, and is by no means the 'right' way. Do it the way you like, but make it readable and consistent.


Postfix and prefix

Some like it, some hate it. This is strictly a matter of taste!

Postfix: old style basics used special signs to identify variable types on every use of that variable, a% for long integers, b& for words, etc. After the initial declaration as in a.l or b.w you're no longer required to specify the type. You're free to do so though. You could use a postfix of your own to identify different variables related to the same thing. A rather silly example:

user_name.s = "this is a test"
user_name_l.l = Len(user_name)
user_name_p.l = 0
While user_name_p < user_name_l
  user_name_p + 1
  Debug Mid(user_name,user_name_p,1)
Wend
For a long time, programmers in various dialects (like C or Pascal) have somewhat looked down upon the postfix variable type declaration in basic. A funny thing is that many modern approaches to programming are doing a similar thing, yet in prefix format... Have a look at the Windows API function SendMessage() and see how they specify their parameters...
LRESULT SendMessage(
  HWND hWnd, // handle of destination window
  UINT Msg, // message to send
  WPARAM wParam, // first message parameter
  LPARAM lParam  // second message parameter
);
Ignore the code itself, it's c(++) but pay good attention to the variable names used: hWind, wParam, lParam... it's a so-called polish notation, where the letters in front of the variable (and part of the variable name, obviously) help the programmer remember what the variable actually does, what type it is, and / or what it is used for. For example, wParam is a variable called 'wParam' (the w is part of the variable name) and contains / expects a word, etc. Geez. That's something Basic programmers have been using for ages!
 

skipFancy stuff

For advanced users / experienced geezers only...

  • structures make it easier to handle multiple aspects / parameters at once
  • enumeration helps us organize and track numbers and list (great thing! check it out!)
  • include files help us split up code in manageable blocks

4.2 Windows
 

There are basically two ways to interact with users: using the console, or using a window.


OpenConsole()

The 'console' is more or less equivalent to command prompt, the old Dos prompt or a Dos box. A console program is still supposed to run under (the) windows (core) though, which means it doesn't work without Windows up and running.

When compiling you should tell PureBasic the type of program you want to generate. This is done via Compiler / Compiler Options / Executable format.

Console programs interact with the user via the console. They don't open a regular Window on the desktop, but use either the console window they were started from, or open their own. Here's an example. Only commands such as Input(), Inkey(), PrintN(), OpenConsole(), CloseConsole(), Delay() etc. should be used for 'true' console programs. See the PureBasic help file for all commands related to consoles.

OpenConsole()
k.s = Input()
PrintN("the user entered "+k.s)
You won't see much after you entered some data, as the screen is updated, and then immediately closed :-)

Input waits for an input. If you want to implement an 'abort' function, you may want to use Inkey. Inkey returns the Ascii value of a single key, if one was pressed.

; survival guide 4_2_150 input
; pb 4.40b3
;
OpenConsole()
PrintN("we're busy... press [esc] to abort")
Repeat
  ;
  ; your code here
  ;
Until Inkey() = Chr(27)
PrintN("done")
Console programs often use commandline parameters to interact with the outside world. There are two modes for consoles, which one you use is set by EnableGraphicalConsole(). See the PureBasic help file for more details.

A note to converts from other (Basic) languages: Inkey only works with consoles!

As of 5.70 LTS you can now add a title to the console window, and define what format any output to the console will have. See the PureBasic help file.


OpenWindow()

<< werkpunt >>

(Note: in PB 3.98 and earlier the sequence of parameters was different!)

Most programs under Windows will open a window (that's a surprise). In PureBasic there are specific commands for doing so.

OpenWindow(1,200,100,500,500,"Test",#PB_Window_SystemMenu)
;
Repeat
Until WaitWindowEvent() = #PB_Event_CloseWindow
I typically use a variable to grab the event, as it just might come in handy for other purposes...
OpenWindow(1,200,100,500,500,"Test",#PB_Window_SystemMenu)
;
Repeat
  event = WaitWindowEvent()
Until event = #PB_Event_CloseWindow
The repeat / until part is necessary to keep the window on screen. More about that in the next section on events.

While you were entering the OpenWindow() command, you may have noticed that the IDE display the syntax of that command on the bottom of the editor window. This way you always have a quick reminder of what parameters a command has. The sample above opens window number 1, at location 200,100 with size 500,500. It displays a system menu (click on the icon on the left upper corner, that's a system menu), and is called "Test".

As you can see, we did not tell PureBasic the values for the two constants #PB_Window_SystemMemu and #PB_Event_CloseWindow. We didn't have to, because PureBasic already knew them. In effect, PureBasic knows a lot of constants, all Windows or PureBasic related. If you are using a constant PureBasic does not know about, it will generate an error. (This is a place where the autocomplete feature of the PB IDE comes in handy.)

As usual, all options for OpenWindow can be found in the help file. Place the cursor on the OpenWindow() command and press [F1].


Refreshing window content

One of the things that still baffles me is that Microsoft Windows doesn't do much when it comes to window contents itself, it doesn't even offer an option to handle things for you...

Let's say one window is overlapped by another, or part of it was hidden from view because something was dragged over it, then it's up to the application to redraw whatever was in the window! This is fairly strange and somewhat stupid, as it forces each and every program to check for 'redraw' messages, and do a redraw if necessary. Even the old Amiga OS took care of window refreshes, and kept a (bitmap copy) of the window contents to restore everything on screen when necessary.

If you draw DIRECTLY on a window YOU (your application) has to take care of the refresh, if you use the PureBasic gadgets such as the ImageGadget() then PureBasic will handle that for you. More about that later.


Multiple windows

Of course you can have multiple windows open. I think I'll get back on that subject later, but here's the quick on it (and a little but important change for people used to 3.94).

In 3.94 you had to specify the window before you could do anything...

UseWindow(window_nr)
x = WindowX()
y = WindowY()
In 4.00 you specify the window with the command:
x = WindowX(window_nr)
y = WindowY(window_nr)
This applies to all window related commands that were assumed to use a previously 'opened' or 'used' window.


4.3 ID versus Number versus Windows handle

Read this section carefully.

The following causes a lot of confusion for PureBasic newcomers... the usage of id's, numbers and handles. (What the frell, until five minutes ago I was confused myself, and sometimes still am...) The new PureBasic help file is a lot clearer on the subject, still here's my old but still somewhat valid :-) explanation...

Let's start with the condensed version:
 

The condensed version

(After some fine cooperation with FR34K :-))

There are two ways to identify an object:

  • a PB specific number (called 'number')
  • an OS specific number (called 'id' or 'handle')
Anyone who understands the above and knows about #PB_Any can skip the rest and get on with the next section on events...

Be clear! The 3.94 documentation is rather misleading as it sometimes talks about 'ID' when it is actually referring to a PB specific number.  Read the documentation carefully and check and doublecheck!4.00 started to fix this but it is still important to check what you are exactly dealing with.

In short:

Don't worry if you don't understand it immediately (I didn't, and I don't want you to feel left out :-)). We'll be lingering a little longer on this subject. Read on...


skipNumbers

... are used as a parameter. They help you identify things or objects. For example, every window has a 'number'. If you look at the OpenWindow() command in the help file, you can see the first parameter is such a 'number'.

OpenWindow(1,200,100,500,500,"Test",#PB_Window_SystemMenu)
In the 3.94 help file these parameters are often marked with a '#' symbol, but not always, and the '#' symbol itself not used in the code itself.

(Personally, I think the # symbol in the documentation should be dropped, it's misleading as # is also used for constants.)

Here the 'number' is important to discern between different windows, window number 1 and window number 2.

OpenWindow(1,200,100,200,200,"Test 1",#PB_Window_SystemMenu)
OpenWindow(2,400,100,200,200,"Test 2",#PB_Window_SystemMenu)
CloseWindow(1)
CloseWindow(2)
Once you have given a window a number, you can refer to that object by using the same number. For example:
ResizeWindow(1,10,10,100,100)
It is possible to let PureBasic generate numbers for you using #PB_Any.
window_nr.i = OpenWindow(#PB_Any,200,100,500,500,"Test 1",#PB_Window_SystemMenu)
Debug window_nr

Handles

Under Windows 'handle' and 'ID' are often (always?) interchangable. Most of the times when a PureBasic command returns an ID, it's actually passing on a Windows handle.


ID's

Microsoft Windows uses so called 'handles' to identify each and every object. Other OS'es may use other concepts. To facilitate interaction with the OS you're working on, these specific identifiers are returned by PureBasic on some commands as 'ID'. Using ID's makes it easier to use OS specific features, for example manipulating Windows 'controls' (in PureBasic called gadgets)...

Some commands return an ID / handle when NOT using #PB_Any. For example the OpenWindow() comes in three flavours!

OpenWindow(1,200,100,500,500,"Window 1",#PB_Window_SystemMenu)             ; just open a window
nr = OpenWindow(#PB_Any,200,100,500,500,"Window 2",#PB_Window_SystemMenu)  ; OpenWindow() returns a window NUBMER
h = OpenWindow(3,200,100,500,500,"Window 3",#PB_Window_SystemMenu)         ; OpenWindow() returns a window HANDLE
I often use the following coding style, to store handle as well as number, as I may need one or the other further down the line:
w_main_nr.i = 1
w_main_h.i = OpenWindow(w_main_nr,200,100,500,500,"Test",#PB_Window_SystemMenu)
If you try to write your code cross-platform, you may want to use the new (PB4.30) .i integer variable type. That way you make sure all handles, counters, pointers etc. are sufficiently large to deal with whatever Windows returns. Or don't specify the type at all, because as of PB4.30 the default type is .i.

Use .i and do not assume the returned pointer / handle / id will be 32 bits long on a 64 bits platform!


#PB_Any

PureBasic can generate numbers for us, for example to take care of window numbering. Instead of specifying the window number, we can let PureBasic generate one for us.

nr1 = OpenWindow(#PB_Any,200,100,500,500,"Test 1",#PB_Window_SystemMenu)
nr2 = OpenWindow(#PB_Any,300,100,500,500,"Test 2",#PB_Window_SystemMenu)
CloseWindow(nr1)
CloseWindow(nr2)
Note again:
  • Note that when using #PB_Any a command returns a unique number. It will NOT return an ID or handle!
  • Some commands use the ID, some use the handle!


If we're running under Microsoft Windows and we need the id or handle for an opened window we can do this:

nr = OpenWindow(#PB_Any,200,100,500,500,"Test",#PB_Window_SystemMenu)
h = WindowID(nr)
#PB_Any is a parameter for some commands. It isn't an independent function or command, so you cannot use it for generating other 'unique' numbers. The following code is wrong, run it and close Window number 1:
; bad use of #PB_Any
; run this, then try to close window 1
;
nr1.l = #PB_Any                                  ; these two lines do NOT generate unique numbers but instead assign
nr2.l = #PB_Any                                  ; the value of the constant #PB_Any to the variables nr1 and nr2
;
Debug nr1                                        ; -1
Debug nr2                                        ; -1
;
OpenWindow(nr2,300,200,500,500,"Test 2",#PB_Window_SystemMenu)     ; OpenWindow() returns a number but we're ignoring it
OpenWindow(nr1,200,100,500,500,"Test 1",#PB_Window_SystemMenu)     ; same for window 1
;
Debug nr1 
Debug nr2 
Debug #PB_Any 
Debug #PB_All 
;
Repeat
  event = WaitWindowEvent()                      ; so this works
Until event = #PB_Event_CloseWindow
;
CloseWindow(nr1)                                 ; we're not closing window 1, but window -1, which means close all
;
Repeat
  event = WaitWindowEvent()                      ; oops, error, all windows have been closed
Until event = #PB_Event_CloseWindow
;
CloseWindow(nr2)                                 ; we'll never end up hereBAD use of the #PB_Any constant
Well, that's an error. Here's a better way, using fixed windows numbers:
; specifying the window number manually
; run this, then try to close window 1 then window 2
;
nr1.l = 1                                        ; ouch... keeping track of the window number ourselves
nr2.l = 2 
;
Debug nr1                                        ; 1
Debug nr2                                        ; 2
;
OpenWindow(nr2,300,200,500,500,"Test 2",#PB_Window_SystemMenu)     ; OpenWindow() returns a handle but we're ignoring it
OpenWindow(nr1,200,100,500,500,"Test 1",#PB_Window_SystemMenu)     ; same for window 1
;
Debug nr1 
Debug nr2 
Debug #PB_Any 
Debug #PB_All 
;
Repeat
  event = WaitWindowEvent()                      ; waiting for the user to close window 1
Until event = #PB_Event_CloseWindow
;
CloseWindow(nr1)                                 ; close window 1
;
Repeat
  event = WaitWindowEvent()                      ; waiting for the user to close window 2
Until event = #PB_Event_CloseWindow
;
CloseWindow(nr2)                                 ; close window 2
But you do not want to keep track of those window numbers yourself, so here's how to let PureBasic generate them for you.
; let PureBasic generate the numbers for us
; run this, then try to close window 1 then window 2
;
nr2 = OpenWindow(#PB_Any,300,200,500,500,"Test 2",#PB_Window_SystemMenu)     ; OpenWindow() returns a handle but we're ignoring it
nr1 = OpenWindow(#PB_Any,200,100,500,500,"Test 1",#PB_Window_SystemMenu)     ; same for window 1
;
Debug nr1 
Debug nr2 
Debug #PB_Any 
Debug #PB_All 
Debug WindowID(nr1)
Debug WindowID(nr2)
;
Repeat
  event = WaitWindowEvent()                      ; waiting for the user to close window 1
Until event = #PB_Event_CloseWindow
;
CloseWindow(nr1)                                 ; close window 1
;
Repeat
  event = WaitWindowEvent()                      ; waiting for the user to close window 2
Until event = #PB_Event_CloseWindow
;
CloseWindow(nr2)                                 ; close window 2


When using #PB_Any I often use the following coding style:

w_main_nr = OpenWindow(#PB_Any,200,100,500,500,"Test",#PB_Window_SystemMenu)
w_main_h = WindowID(w_main_nr)
Sometimes enumeration is a good alternative to #PB_Any.


4.4 Events
 

The core of PureBasic Windows programming is the event loop, where your program interacts with the user. Pay attention, people, this is essential!

As Windows is a multitasking environment (more or less, ahum), our program should wait, do nothing, and return all possible resources back to the system when idling away... all the time waiting for events to happen....

This means, in essence, that ANY program under windows contains the following loop (pseudocode):

repeat
  wait for an event
  what kind of event?
    if event was a then do this
    if event was b then do that
keep on repeating until the program is ended or done
This kind of loop is also called an 'eventloop'. We keep repeating the loop and process any incoming events or messages. Many things can generate events. Here are just a few...
  • Windows itself (try to move a window etc.)
  • Gadgets (called 'controls' in some other languages), for example a user clicking on a button
  • menu's and toolbars
  • timers, communication messages, other applications, and the list goes on...
On this page you will find some examples of event loops, immediately below, and elsewhere in the sections  4 4 Events, 4.5 Gadgets, and 4.6 Menus and toolbars.


WaitWindowEvent()

In PureBasic, you can either use callbacks (for advanced users), WindowEvent(), or WaitWindowEvent().

WaitWindowEvent() does exactly what is says: it halts execution until an event passes. If an event passes, it returns a code. Our program should check that code to decide if it has to take certain action or not.

; survival guide 4_4_100 eventloop
; pb 4.40b3
;
#False = 0
#True = 1
;
OpenWindow(1,200,100,500,500,"Test",#PB_Window_SystemMenu|#PB_Window_SizeGadget)
;
done = #False
Repeat
  event = WaitWindowEvent()
  Select event
    Case #PB_Event_CloseWindow
      Debug "close button"
      done = #True
    Case #PB_Event_MoveWindow
      Debug "window moved"
    Case #PB_Event_SizeWindow
     Debug "window resized"
    Default
      ; Debug event
  EndSelect
Until done = #True
;
CloseWindow(1)
Debug "window closed"
Run the code above, and not much happens. The program is waiting for events. Now resize the window and you will see events show up in the debug window.. Exit the program, now remove the semicolon on line 18 (in front of the Debug) and run again... you will see a much larger number of events pass which is correct. Windows is sending messages (events) to our window for all sorts of things. It's up to us to decide if we process them or not.

WaitWindowEvent() allows one optional parameter, if specified the program will wait for 'n' milliseconds before continueing, regardless if there was a message or not.

Under older versions of PureBasic I never saw the 'window moved' message, but it seems to be there these days (PB4.30b1). Not all messages generated by Windows are passed on, and not all messages can be seen here... Fortunately, in most cases it doesn't matter much and you can always use a 'hook' once you're digging deep(er) into PureBasic and Windows.

Al events PureBasic knows about are defined in constants with the form #PB_Event_xxxxx. Microsoft Windows generates more events, and these are also defined in constants like #WM_MOVE. Be aware that PureBasic on Linux doesn't see and / or handle those Microsoft Windows specific messages.


WindowEvent()

; survival guide 4_4_150 windowevent
; pb 4.40b3
;
#False = 0
#True = 1
;
OpenWindow(1,200,100,500,500,"Test",#PB_Window_SystemMenu|#PB_Window_SizeGadget)
;
done = #False
Repeat
  event = WindowEvent()
  Select event
    Case 0
     Delay(50)
     Debug "."
    Case #PB_Event_CloseWindow
      Debug "close button"
      done = #True
    Case #PB_Event_MoveWindow
      Debug "window moved"
    Case #PB_Event_SizeWindow
      Debug "window resized"
    Default
      Debug event
  EndSelect
Until done = #True
;
CloseWindow(1)
Debug "window closed"
The code above shows the use of WindowEvent(), WindowEvent() does NOT wait for an event, so the debug window will shot a lot of dots (no event means WindowEvent() will return 0). On line 19 you will see an extra Delay() instruction. This will tell our program to wait (at least) 50 milliseconds, and during that time return control back to Windows (and any other programs). If we don't do this, we would tie up all Windows resources, and not give Windows a chance to change the screen, display new information, or do any other necessary stuff. When you use WindowEvent() instead of WaitWindowEvent() it's up to you to make sure Windows gets a chance to do other things.


Other events

Not only a window (or the desktop in general) can generate an event, there could be more reasons why your program receives a message... Some are WinAPI related, for example a message when a timer expires, network data has come in, etc. but... Messages can be generated by all sorts of events... coming from windows, menus, gadgets, or other operating system stuff (like timers, screen refreshes, and what not).

Gadgets are items such as buttons, panels, edit boxes etc. (In Microsoft Windows called 'controls'). Menus and toolbars are other items that can generate events.

  • WaitWindowEvent() waits for any event, and returns that event
  • EventMenu() returns the id of the menu or toolbarbutton that generated the event
  • EventGadget() returns the id of the gadget that generated the event
  • EventWindow() returns the id of the window that the event came from
  • EventType() tells you what category of event it is
  • EventTimer() gives the number of the timer that triggered an event
  • EventwParam() is officially no longer supported, but still works under Windows (and that's good because it's still needed)

Don't waste timeslices!

Here's another example which shows how to do or check things continuously, without bothering Windows too much. If our program doesn't need to do anything, it's not going to run an endless loop wasting resources, it just gracefully hands control back to Windows until there's something to do.

If there are messages they will be rapidly processed. If there are no messages, the program will wait (for 250 msec) until it executes the event loop. And on every timer event (set by using AddWindowTimer() to once every 2000 msec) a timer message will be processed.
 

; survival guide 4_4_200 windowevent
; pb 4.51
;
EnableExplicit
;
Enumeration
  ;
  ; actions
  ;
  #continue
  #exit
  ;
  ; menu entries
  ;
  #f_stop
  ;
  ; gui elements
  ;
  #w_main
  #g_counter_all
  #g_counter_null
  #g_counter_timer
  ;
  ; timers
  ;
  #t_timer
  ;
EndEnumeration
;
; close the window or press the 's' key to abort
;
OpenWindow(#w_main,0,0,240,140,"ShortcutGadget", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
TextGadget(#g_counter_all,10,10,200,20,"all events 0")
TextGadget(#g_counter_null,10,30,200,20,"null events 0")
TextGadget(#g_counter_timer,10,50,200,20,"timer events 0")
AddKeyboardShortcut(#w_main, #PB_Shortcut_S, #f_stop)
AddWindowTimer(#w_main,#t_timer,2000)
;
Global counter_all = 0
Global counter_null = 0
Global counter_timer = 0
;
Global action, event, eventmenu, eventtimer
;
Repeat
  ;
  ; grab events, wait maximal 250 msec for an event
  ;
  event = WaitWindowEvent(250)
  eventmenu = EventMenu()
  eventtimer = EventTimer()
  ;
  ; we'll keep looping until action has been set to #exit
  ;
  action = #continue
  ;
  Select event
  Case #PB_Event_CloseWindow
    ;
    ; close window button
    ;
    action = #exit
    ;
  Case #PB_Event_Menu
    ;
    ; menu, toolbar button or keyboard shortcut
    ;
    Select eventmenu
    Case #f_stop
      action = #exit
    EndSelect
    ;
  Case #PB_Event_Timer
    ;
    ; timer events
    ;
    Select eventtimer
    Case #t_timer
      counter_timer = counter_timer+1
      SetGadgetText(#g_counter_timer,"timer events "+Str(counter_timer))
    EndSelect
    ;
  Case 0
    ;
    ; called every time waitwindowevent() timed out
    ;
    counter_null = counter_null+1
    SetGadgetText(#g_counter_null,"null events "+Str(counter_null))
    ;
  Default
    ;
    ; called every time anything else caused the loop to run
    ;
  EndSelect
  ;
  ; called on every event loop (on each windows message, waitwindowevent() timeout, timer message etc.)
  ;
  counter_all = counter_all+1
  SetGadgetText(#g_counter_all,"all events "+Str(counter_all))
  ;
Until action = #exit



4.5 Gadgets
 

The core of PureBasic Windows programming is the event loop, where your program interacts with the user. Simply put: users click on things on the screen, and we use similar things on screen to return information to the users. Pay attention, people, this is essential!

Gadgets are building blocks, that we use to piece together a nice GUI, or Graphical User Interface. Gadgets work the same, no matter if we program under Microsoft Windows, Amiga OS or Linux, although the gadgets themselves use the underlaying OS functionality.
 

Gadgetlists...

PureBasic keeps track of all gadgets in a gadgetlist. In the past you had to specify one for each window using the command CreateGadgetList(). As of 4.30 that is no longer necessary, as the OpenWindow() command automatically creates a gadgetlist.

The helpfile contains more information on all possible gadgets and their use.

window_nr = OpenWindow(#PB_Any,200,100,500,500,"Test",#PB_Window_SystemMenu|#PB_Window_SizeGadget)
window_h = WindowID(window_nr)
ButtonGadget(1,10,50,90,20,"Button 1")
ButtonGadget(2,10,70,90,20,"Button 2")
;
Repeat
  event = WaitWindowEvent()
  Select event
    Case #PB_Event_Gadget
      Select EventGadget()
        Case 1
          Debug "gadget 1"
        Case 2
          Debug "gadget 2"
      EndSelect
  EndSelect
Until event = #PB_Event_CloseWindow
As you can see in the code above, I have given each button a unique number. If you want you can use #PB_Any or Enumeration to have PureBasic generate those numbers.

Under PB4.20 you had to create a gadgetlist for each and every window, like this:

; pb 4.20
;
OpenWindow(1,200,100,500,500,"Test",#PB_Window_SystemMenu)
CreateGadgetList(WindowID(1))
As of 4.30 the OpenWindow() command now automatically creates a gadgetlist, so you can leave the CreateGadgetList() command out.
; pb 4.30
;
OpenWindow(1,200,100,500,500,"Test",#PB_Window_SystemMenu)
Gadgetlists are still created per window. You can tell PureBasic which window to create the gadgets on, using UseGadgetList():
; survival guide 4_5_200 gadgetlist
; pb 4.40b3
;
OpenWindow(1,100,100,200,180,"Test1")     ; opens a window and create and use a gadgetlist for window 1
ButtonGadget(1,20,20,160,60,"Button 1")   ; create a button on window 1
;
OpenWindow(2,400,100,200,180,"Test2")     ; opens a window and create and use a gadgetlist for window 2
ButtonGadget(2,20,20,160,60,"Button 2")   ; create a button on window 2
;
UseGadgetList(WindowID(1))                ; use the gadgetlist that belongs to window 1
ButtonGadget(3,20,100,160,60,"Button 3")  ; create another button on window 1
;
exit = #False
While exit = #False
  event = WaitWindowEvent()
  Select event
  Case #PB_Event_CloseWindow
    exit = #True
  EndSelect
Wend


Check for gadget events

  • WaitWindowEvent() returns a #PB_Event_Gadget on a gadget event
  • EventGadget() returns the number of the gadget that caused the event


Multiple windows

  • all windows share one gadgetlist
  • gadget numbers, and thus ID's, have to be unique
  • two gadgets can never have the same NR or ID, not even when they are displayed on different windows
This makes some sense, as you can have multiple windows open. Use one eventloop using EventGadget() and you don't have to check what window a gadget belongs to catch its events (though you still can).


4.6 Menus and toolbars


Menus

You can build one or more menus, multiple levels deep, for each window you open.

; survival guide 4_6_200 menus
; pb 4.40b3
;
window_nr = OpenWindow(#PB_Any,200,100,500,500,"Test",#PB_Window_SystemMenu|#PB_Window_SizeGadget)
window_h = WindowID(window_nr)
;
CreateMenu(#PB_Any,window_h)
MenuTitle("Project") 
MenuItem(1, "&Exit") 
;
Repeat
  event = WaitWindowEvent()
  Select event
    Case #PB_Event_Menu
      Select EventMenu()
        Case 1
         Debug "menu 1: exit"
    EndSelect
  EndSelect
Until event = #PB_Event_CloseWindow
The above generates a window with a menu. Each additional window needs its own CreateMenu() statement. See the help file for all posible menu commands.

The first parameter after MenuItem() is the menuitem id. You decide what number it is. You may share the number with a toolbar button, thus creating 'shortcuts' for used menu functions.


Toolbars

A toolbar is very similar to a menu in use and returned values. Here are toolbars and menus in one sample:

 
; survival guide 4_6_300 toolbars
; pb 4.40b3
;
window_nr = OpenWindow(#PB_Any,200,100,500,500,"Test",#PB_Window_SystemMenu|#PB_Window_SizeGadget)
window_h = WindowID(window_nr)
;
CreateMenu(#PB_Any,window_h)
MenuTitle("Project") 
MenuItem(1, "&Open") 
;
CreateToolBar(#PB_Any,window_h)
ToolBarStandardButton(1, #PB_ToolBarIcon_Open)
ToolBarStandardButton(2, #PB_ToolBarIcon_Save)
;
Repeat
  event = WaitWindowEvent() 
 Select event
    Case #PB_Event_Menu
      Select EventMenu()
        Case 1
          Debug "menuitem 1 OR toolbarbutton 1: open"
       Case 2
          Debug "toolbarbutton 2: save"
     EndSelect
  EndSelect
Until event = #PB_Event_CloseWindow
As you can see, you can use toolbars and menus independently with each having their own id, or to access the same functions by sharing an id. It's up to you. See the help file for all possible commands.

Note: use 24 bit images to avoid alpha channel issues under 4.40b1 and later.
 

Sharing numbers

  • menuitem's and toolbarbuttons sharing the same number generate the same EventMenu()
  • changes to a menu (for example disabling it) will change all menus using that same number
  • changes to a toolbarbutton will affect all toolbarbuttons that use that same number


Check for menu and toolbar events

  • WaitWindowEvent() returns a #PB_Event_Menu
  • EventMenu() returns the menuitem or toolbarbutton number


Multiple windows

  • each window has it's own OpenWindow(), CreateMenu() and CreateToolbar()
  • menuitem numbers and toolbarbutton numbers do not have to be unique
  • menuitem numbers and toolbarbutton numbers should be unique if the same button or menu on another window should generate a different message
You can use one loop to catch all events for all different windows, menus and toolbars... It's easier to keep all numbers unique (hey, that's why enumaration is so handy).


4.7 Include
 

Not all code has to be located in one single file. We can split up a larger project in smaller components, that will be added together when compiled.

The IncludeFile command tells PureBasic to process another file, then continue the current file. XIncludeFile does the same, but it will only include any specified files once, no matter how many XIncludeFile statements are used.

In the past PureBasic suggested the use of the extension .PBI for include files. I could never figure out why, and neither could the developers as it is no longer suggested :-)

Two additional commands worth mentioning are IncludeBinary and IncludePath. Please see the helpfile for more information.

(Guess this is one of the shortest sections so far :-))
 


4.8 Enumeration
 

Although basically very simple, enumeration is a very powerful tool... In PureBasic you can declare constants like this:

#f_file_open = 0
#f_file_save = 1
#f_file_close = 2
#f_file_exit = 3
If you would need to define a large number of constants, each with its own unique number (think about lists of images, menu entries, file numbers, etc.) this would mean you a. would have to type it all in, and b. would have to make sure no duplicates exist. In PureBasic, there is a very easy and powerful option: enumeration.

All constants listed between Enumeration and EndEnumeration are given an increasing value. That's it. That's it?

Enumeration
  #f_file_open
  #f_file_save
  #f_file_close
  #f_file_exit
EndEnumeration
Have a look at the code above. By itself, it doesn't do much. Once compiled, the constant #f_file_open will get the value 0, #f_file_save will be 1, etc.

Let's assume we are writing a small text-editor. We will need to be able to load and save files. By creating a list of the functions we need as constants, we have an easy way to match any event with a function, or match any menuitem or toolbarbutton to a function... Instead of actually specifying a number for each function, we use a constant. (To remind myself it's a function I've added an f_ prefix).

Now let's say we have created a toolbar and a menu, then we could add some items to them, the menu...

MenuItem(#f_file_open,"&Open")
... and the toolbar...
ToolbarStandardButton(#f_file_open,#PB_ToolBarIcon_Open)
Now both the menuitem and toolbar button generate a message with a EventMenu() of #f_file_open. Instead of checking the event against the number 0 we check it against the constant, making our code much more readable.

Another example, we have created a program with two windows, and decide to add another one. But we don't want to keep track of which window has what number. Easy, what we do is, at the start of our code, we add a small Enumeration block, where we give every window it's own number.

Enumeration
  #w_main_nr
  #w_properties_nr
EndEnumeration
This could be used for gadgets, filehandles, etc. It will help us avoid duplicate gadget numbers etc. There are countless other similar examples. Again, enumeration helps us to quickly generate lists of constants and helps us avoid reusing numbers, that's all there is to it.
 

Start value

Normally Enumeration starts with 0, but it can start with any arbitrary value, as hown here:

Enumeration 30
  #constanta          ; 30
  #constantb          ; 31
EndEnumeration


Chaining

Every new call to Enumeration would reset the counter, but you can continue (chain) the previous numbering...

Enumeration
  #constanta          ; 0
  #constantb          ; 1
EndEnumeration
;
Enumeration
  #constantc          ; 0
  #constantd          ; 1
EndEnumeration
;
Enumeration #PB_Compiler_EnumerationValue
  #constante          ; 2
  #constantf          ; 3
EndEnumeration


Step value.

Normally Enumeration starts with 0, but it can start with any arbitrary value, as shown here:

Enumeration 30 Step 5
  #constanta          ; 30
  #constantb          ; 35
EndEnumeration

4.9 Visual Designer.
 

I should play with the new visual designer. But simply haven't done so...


4.10 File system and FTP
 

(FTP is a little further down.)
 

Filesystem

Since 3.94 things have greatly improved. We don't have to use UseFile(), UseDirectory() etc. anymore, and there are some other improvements as well.

If you look at the PureBasic help file, you'll find two libraries: one called 'File' and the other 'FileSystem'. Together they allow us to manage files, folders, and all data in there.


Testing if a file exists

FileSize() returns the size of a file, or -1 if it doesn't exist, or -2 if the given path+name was actually a folder.

Don't worry. Everybody asks this same question some time in the forums :-)


Opening and closing files

Every (open) file is identified by a number. As usual, you can either specify these yourself, or let PureBasic do so.

OpenFile() will open an existing file, or create a new one if there wasn't one yet. The filename has to follow the restrictions of the OS you are running PureBasic on.

  file_nr = OpenFile(#PB_Any,"test.txt")
  CloseFile(file_nr)

Note that OpenFile may fail on read-only devices, so if you plan to just read from a file it's better to use ReadFile().

  file_nr = ReadFile(#B_Any,"test2.txt")

CreateFile() is used to create and open new files. If the file already existed, it will be emptied then opened.

  file_nr = CreateFile(#B_Any,"test2.txt")

Unfortunately, PureBasic does not allow you to open / close files with the 'shared' flag, unless you go the WinApi way :-(


File buffers

PureBasic will create a buffer for each file to speed up reading and writing. For each file open you can set the size of the buffer using:

  file_nr = OpenFile(#PB_Any,"test.txt")
  FileBufferSize(file_nr,16384)           ; use a 16 kB buffer for this file
  CloseFile(file_nr)

Data is NOT written to the disk, unless you either close the file, or you force a 'flush' with FlushFileBuffers().

If the same file is opened by mutliple aplications, or if you are reading AND writing to the same file, you'd better set buffer size to 0:

  file_nr = OpenFile(#PB_Any,"test.txt")
  FileBufferSize(file_nr,0)               ; sorry, no buffering today
  CloseFile(file_nr)


Reading and writing data

PureBasic has a number of standard functions to read from and write to files.

WriteByte(...)             ; write a single byte
WriteCharacter(...)        ; write either a byte or a word (2 bytes)
WriteAsciiCharacter(...)   ; write an unsigned byte
WriteUnicodeCharacter(...) ; write an unsigned word
WriteWord(...)             ; write a word (2 bytes)
WriteLong(...)             ; write a long (4 bytes)
WriteInteger(...)          ; write an integer (32 or 64 bits)
WriteQuad(...)             ; write a quad (8 bytes)
WriteFloat(...)            ; write a float (4 bytes)
WriteDouble(...)           ; write a double (8 bytes)
ReadByte(...)              ; read a single byte
ReadCharacter(...)         ; read either a byte or a word (2 bytes)
ReadAsciiCharacter(...)    ; read an unsigned byte
ReadUnicodeCharacter(...)  ; read an unsigned word
ReadWord(...)              ; read a word (2 bytes)
ReadInteger(...)           ; read an integer (32 or 64 bits)
ReadLong(...)              ; read a long (4 bytes)
ReadQuad(...)              ; read a quad (8 bytes) 
ReadFloat(...)             ; read a float (4 bytes)
ReadDouble(...)            ; read a double (8 bytes)
Remember that all these functions start writing the lowest byte first ('little-endian').

ReadCharacter() is a special case. It will write either 8 bits (1 byte) or 16 bits (2 bytes) depending on the Unicode mode.

Of course there is a function for reading strings:

var.s = ReadString(...)        ; reads a string
ReadString() continues reading data from the specified file until it encounters an 'end of line' condition: this can be either one of the following characters CR/LF ($0D $0A), CR ($0D), LF ($0A), NULL ($00), or the end of the file.

This also means that if you want to store multiple strings in a file, you must make sure they are seperated from each other. You can do this by either adding your own 'end of line' character, or by using a variant of WriteString() called WriteStringN():

CreateFile(1,"test.txt")
WriteString(1,"this is line 1")
WriteString(1,"this is line 2")
WriteByte(1,0)
WriteStringN(1,"this is line 3")
WriteString(1,"this is line 4")
CloseFile(1)
;
ReadFile(1,"test.txt")
Debug ReadString(1)
Debug ReadString(1)
Debug ReadString(1)
CloseFile(1)
The code above will generate a tekst file and write some strings to it. As there is no 'seperation' between 'line 1' and 'line 2' they will be appended when reading back the file. WriteStringN() actually adds a CR/LF when writing a string:
WriteStringN(1,"this is line 1")
Is equivalent to:
WriteString(1,"this is line 1")
WriteByte(1,$0D)
WriteByte(1,$0A)
You can specify how information is stored, by adding specific flags. See the section on Unicode.


Copying, deleting, renaming files

See the help file for more information on:

  • CopyFile()
  • DeleteFile()
  • RenameFile()

Folder contents and FTP

The FTP library provides easy access to FTP servers and files storen on them. You can send files to the server, delete them, create folders etc. You can not directly write to them or read from them. See the help file for the different commands.

Getting the contents from a folder on an FTP server is very similar to getting them from a local folder, ie. using the regular file system functions.

; survival guide 4_10_100 file listing and ftp
; pb 4.40b3
;
Enumeration
  #ftp_nr
  #dir_nr
EndEnumeration
;
; listing the contents of a folder / directory
;
Debug ""
Debug "*** filesystem ***"
Debug ""
;
Debug GetCurrentDirectory()                                      ; current folder
;
ExamineDirectory(#dir_nr,"c:\","")                               ; get all folder contents
While NextDirectoryEntry(#dir_nr) <> 0
  Debug DirectoryEntryName(#dir_nr)
Wend

; grabbing stuff from an ftp account is pretty similar to grabbing folder contents
;
;
InitNetwork()
Debug ""
Debug "*** ftp ***"
Debug ""
;
If OpenFTP(#ftp_nr,"xxx.xxxxx.xx","xxxxxxxx","xxxxxxxx") <> 0   ; open ftp connection
  ;
  Debug GetFTPDirectory(#ftp_nr)                                ; get current folder
  SetFTPDirectory(#ftp_nr,"WWW")                                ; change to a subfolder
  ;
  ExamineFTPDirectory(#ftp_nr)                                  ; get all folder contents
  While NextFTPDirectoryEntry(#ftp_nr) <> 0
    Debug FTPDirectoryEntryName(#ftp_nr)
  Wend
  ;
  Debug "closing ftp connection"
  CloseFTP(#ftp_nr)                                             ; close ftp connection
EndIf
;
Debug ""
Debug "*** done ***"
;

Of course, you'll have to use your own FTP server, user and password information :-)
 

Sending and receiving files

You cannot read or write data to a file on a remote FTP server, you can only send or receive complete files. Some FTP servers may support paths in filenames, some may not.

To move to a specific folder on the FTP server use SetFTPDirectory().

To send a specific file from the local machine to the remote FTP server use SendFTPFile(). See the help file for more details, but frankly that's pretty much there is to it.
 

More on FTP and PureBasic...


4.11 Desktop and Mouse
 

Desktop

If you're a lucky bastard you got more than one screen. Isn't it great to watch your favourite movie on the second screen whilst you are coding PureBasic on the first? Or you could watch two movies at the same time! Or code two programms at the same time! :-) Well, maybe not, but still it's great to have a second monitor at hand.

Windows alles multiple monitors, and calls them 'Desktops'. (There are some cheesy flavours of 'desktops' which are NOT monitors, but I've never encountered any in the wild, so I'll ignore that, and stick to the following adagio: 2 windows equals 2 desktops.)

As of 4.31 we got the necessary commands to deal with multiple monitors (and I no longer have to code WinAPI stuff myself when dealing with them, pfew...)

Each desktop (monitor) has it's own origin (x/y coordinates of top left corner) as well as height, size, refresh frequency, colour depth etc. Before you can retrieve those parameters you first need to call ExamineDesktops() once. The first desktop also known as the primary monitor is always located at 0,0.
 

Mouse

Mouse coordinates mean little if you don't know what they refer to. There are different commands for retrieving the mouse pointer position. Where it's on the desktop or where it's on a window. This makes sense as windows could be moved around :-) User interaction is communicated with your programm through 'events', so that's how you can capture mouse clicks if you have to.

Screens are whole different beasts. The mouse pointer in there has little to do with the regular mouse pointer, and querying it is a whole different ball game. (And some day I am going to talk about it in detail, just not now. Just keep in mind that they work differently.

Here's a little list with somewhat related or comparable commands for windows, desktops, and the mouse, just to point you in the right direction and to make sure you won't mix up desktop, window and screen related commands...
 

ExamineDesktops()
DesktopX()
DesktopY()
DesktopWidth()
DesktopHeight()
DesktopMouseX()
DesktopMouseY()
OpenWindow()
WindowX()
WindowY()
WindowWidth()
WindowHeight()
WindowMouseX()
WindowMouseY()
GetActiveWindow()
WaitWindowEvent()
WindowEvent()
EventType()
InitScreen()
OpenScreen()
OpenWindowedScreen()
MouseX()
MouseY()
IsScreenActive()
ExamineMouse()
MouseButton()

Okay, that's all for Primer II. Feel free to send me your comments on the text on this page. Now go on to the advanced PureBasic topics...

For any questions related to PROGRAMMING in PureBasic do not ask me but visit the forum! (There you will find people who are much more knowledgeable about PureBasic than I am.) This Survival Guide is more about getting to know PureBasic than it is about solving problems in PureBasic...